package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 聊天室的服务端 版本5
 *
 */
public class Server5 {
    private ServerSocket ServerSocket;
    //V4:该集合用于保存所有客户端的输出流，用于广播消息
    private List<PrintWriter> allOut = new ArrayList<>();

    public Server5() {
        try {
            System.out.println("正在启动服务端");
            ServerSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            int i = 1;
            while (true) {
                System.out.println("等待接收客户端");
                Socket socket = ServerSocket.accept();//接电话
                System.out.println(i + "个客户端已连接");
                i++;
                //启动一个线程与该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //main方法是静态方法，不能访问普通方法
        //调用构造器初始化数据
        Server5 server = new Server5();
        server.start();
    }

    /**
     * 该线程任务负责与指定的客户端交互 clienthandler：客户端处理器
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;//记录该客户端的IP地址

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取远端计算机的IP地址的字符串格式
            host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            //v5:提高pw的作用域，一遍finally中可以将其从allOut集合中删除
            PrintWriter pw = null;
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                //V3:通过socket获取输出流用于给客户端发送消息
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                 pw = new PrintWriter(bw, true);

                //V5：多线程向集合中添加元素可能产生并发安全问题，因此必须同步执行
                synchronized (allOut) {//通常同步监视器对象就选取临界资源
                    allOut.add(pw);
                }
                //V5：广播消息，该客户端上线了
                sendMessage(host+"上线了，当前人数为："+allOut.size());

                String message;
                while ((message = br.readLine()) != null) {
                    //V5：读取到客户端消息广播时改为调用sendMessage方法
                    sendMessage(host+"说："+message);
                }
            } catch (IOException e) {

                //V5:在finally中处理客户端断开后的操作
            }finally {

                //将客户端的输出流从allOut集合中删除
                synchronized(allOut) {
                    allOut.remove(pw);
                }
                //广播该客户端下线了
                sendMessage(host+"下线了，当前在线人数："+allOut.size());
                try {
                    //关闭socket，释放资源
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * V5的功能，实现广播消息的方法：
         * 将消息转发给所有客户端
         */
        private void sendMessage(String message){
            System.out.println(message);
            //V5:新循环是迭代器，迭代器要求遍历过程不能用集合方法增删元素，
            // 所以需要和上面的增删互斥
            synchronized (allOut) {
                for (PrintWriter O : allOut) {
                    System.out.println(message);
                }
            }
        }
    }
}
